/*
 * MainFrame.java
 *
 * Copyright 2006 Sun Microsystems, Inc. ALL RIGHTS RESERVED Use of
 * this software is authorized pursuant to the terms of the license
 * found at http://developers.sun.com/berkeley_license.html.
 *
 */

package swingworker;

import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Properties;
import javax.swing.DefaultListModel;
import javax.swing.GroupLayout;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JProgressBar;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.GroupLayout.Alignment;
import javax.swing.LayoutStyle.ComponentPlacement;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

/**
 * MainFrame is the launch point for the demo. It provides the main method that
 * runs on the initial thread, and it provides a JFrame subclass that runs on
 * the Swing event dispatch thread.
 * 
 * 
 * @author John O'Conner
 */
public class MainFrame extends JFrame {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * Creates new form MainFrame
	 */
	public MainFrame() {
		initComponents();
	}

	/**
	 * This method is called from within the constructor to initialize the form.
	 * WARNING: Do NOT modify this code. The content of this method is always
	 * regenerated by the Form Editor.
	 */
	private void initComponents() {
		lblSearch = new javax.swing.JLabel();
		txtSearch = new javax.swing.JTextField();
		lblImageList = new javax.swing.JLabel();
		scrollImageList = new javax.swing.JScrollPane();
		listModel = new DefaultListModel();
		renderer = new ImageInfoRenderer();
		listImages = new JList(listModel);
		listImages.setCellRenderer(renderer);
		lblSelectedImage = new javax.swing.JLabel();
		lblImage = new javax.swing.JLabel();
		progressMatchedImages = new javax.swing.JProgressBar();
		listenerMatchedImages = new ProgressListener(progressMatchedImages);
		progressSelectedImage = new javax.swing.JProgressBar();
		listenerSelectedImage = new ProgressListener(progressSelectedImage);

		setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		setTitle("Image Search");
		lblSearch.setText("Search");

		txtSearch.addKeyListener(new java.awt.event.KeyAdapter() {
			public void keyPressed(java.awt.event.KeyEvent evt) {
				txtSearchKeyPressed(evt);
			}
		});

		lblImageList.setText("Matched Images");

		listImages.setModel(listModel);
		listImages.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		listImages.setCellRenderer(renderer);
		listImages.addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent evt) {
				listImagesValueChanged(evt);
			}
		});

		scrollImageList.setViewportView(listImages);

		lblSelectedImage.setText("Selected Image");

		lblImage.setBorder(javax.swing.BorderFactory
				.createLineBorder(new java.awt.Color(204, 204, 204)));
		lblImage.setFocusable(false);
		lblImage.setMaximumSize(new java.awt.Dimension(500, 500));
		lblImage.setMinimumSize(new java.awt.Dimension(250, 250));
		lblImage.setOpaque(true);
		lblImage.setPreferredSize(new java.awt.Dimension(500, 250));

		GroupLayout layout = new GroupLayout(getContentPane());
		getContentPane().setLayout(layout);
		layout
				.setHorizontalGroup(layout
						.createParallelGroup(Alignment.LEADING)
						.addGroup(
								Alignment.TRAILING,
								layout
										.createSequentialGroup()
										.addContainerGap()
										.addGroup(
												layout
														.createParallelGroup(
																Alignment.TRAILING)
														.addComponent(
																lblImage,
																Alignment.LEADING,
																GroupLayout.DEFAULT_SIZE,
																462,
																Short.MAX_VALUE)
														.addComponent(
																scrollImageList,
																GroupLayout.DEFAULT_SIZE,
																462,
																Short.MAX_VALUE)
														.addGroup(
																Alignment.LEADING,
																layout
																		.createSequentialGroup()
																		.addGroup(
																				layout
																						.createParallelGroup(
																								Alignment.LEADING)
																						.addComponent(
																								lblImageList)
																						.addComponent(
																								lblSelectedImage))
																		.addPreferredGap(
																				ComponentPlacement.RELATED)
																		.addGroup(
																				layout
																						.createParallelGroup(
																								Alignment.LEADING)
																						.addComponent(
																								progressMatchedImages,
																								GroupLayout.DEFAULT_SIZE,
																								350,
																								Short.MAX_VALUE)
																						.addComponent(
																								progressSelectedImage,
																								GroupLayout.DEFAULT_SIZE,
																								350,
																								Short.MAX_VALUE)))
														.addGroup(
																Alignment.LEADING,
																layout
																		.createSequentialGroup()
																		.addComponent(
																				lblSearch)
																		.addPreferredGap(
																				ComponentPlacement.RELATED)
																		.addComponent(
																				txtSearch,
																				GroupLayout.DEFAULT_SIZE,
																				411,
																				Short.MAX_VALUE)))
										.addContainerGap()));
		layout
				.setVerticalGroup(layout
						.createParallelGroup(Alignment.LEADING)
						.addGroup(
								layout
										.createSequentialGroup()
										.addContainerGap()
										.addGroup(
												layout
														.createParallelGroup(
																Alignment.BASELINE)
														.addComponent(lblSearch)
														.addComponent(
																txtSearch,
																GroupLayout.PREFERRED_SIZE,
																GroupLayout.DEFAULT_SIZE,
																GroupLayout.PREFERRED_SIZE))
										.addPreferredGap(
												ComponentPlacement.RELATED)
										.addGroup(
												layout
														.createParallelGroup(
																Alignment.LEADING)
														.addComponent(
																lblImageList)
														.addComponent(
																progressMatchedImages,
																GroupLayout.PREFERRED_SIZE,
																GroupLayout.DEFAULT_SIZE,
																GroupLayout.PREFERRED_SIZE))
										.addPreferredGap(
												ComponentPlacement.RELATED)
										.addComponent(scrollImageList,
												GroupLayout.PREFERRED_SIZE,
												235, GroupLayout.PREFERRED_SIZE)
										.addPreferredGap(
												ComponentPlacement.RELATED)
										.addGroup(
												layout
														.createParallelGroup(
																Alignment.LEADING)
														.addComponent(
																lblSelectedImage)
														.addComponent(
																progressSelectedImage,
																GroupLayout.PREFERRED_SIZE,
																GroupLayout.DEFAULT_SIZE,
																GroupLayout.PREFERRED_SIZE))
										.addPreferredGap(
												ComponentPlacement.RELATED)
										.addComponent(lblImage,
												GroupLayout.DEFAULT_SIZE, 305,
												Short.MAX_VALUE)
										.addContainerGap()));
		pack();
	}// </editor-fold>//GEN-END:initComponents

	/**
	 * Handles image list selection events. Initiate the retrieval of a larger
	 * image for the selected thumbnail.
	 */
	private void listImagesValueChanged(ListSelectionEvent evt) {
		// GEN-FIRST:event_listImagesValueChanged
		// don't do anything if this is just a changing list
		// being populated by thumbnail images
		if (evt.getValueIsAdjusting() || listImages.isSelectionEmpty()) {
			return;
		}
		// handle selection made by the user
		int selectedIndex = listImages.getSelectedIndex();
		if (selectedIndex >= 0) {
			ImageInfo info = (ImageInfo) listImages.getSelectedValue();
			String id = info.getId();
			String server = info.getServer();
			String secret = info.getSecret();
			// no need to search an invalid thumbnail image
			if (id == null || server == null || secret == null) {
				return;
			}
			String strImageUrl = String.format(IMAGE_URL_FORMAT, server, id,
					secret);
			retrieveImage(strImageUrl);
		}
	}// GEN-LAST:event_listImagesValueChanged

	private void retrieveImage(String imageUrl) {
		// SwingWorker objects can't be reused, so
		// create a new one as needed.
		ImageRetriever imgRetriever = new ImageRetriever(lblImage, imageUrl);
		progressSelectedImage.setValue(0);
		// listen for changes in the "progress" property
		// we can reuse the listener even though the worker thread
		// will be a new SwingWorker
		imgRetriever.addPropertyChangeListener(listenerSelectedImage);

		progressSelectedImage.setIndeterminate(true);

		// tell the worker thread to begin with this asynchronous method
		imgRetriever.execute();
		// this event thread continues immediately here without blocking

	}

	/**
	 * Begin searching for images with search text in either the title or
	 * description.
	 */
	private void txtSearchKeyPressed(KeyEvent evt) {
		// GEN-FIRST:event_txtSearchKeyPressed
		if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
			String strSearchText = txtSearch.getText();
			if (strSearchText != null && strSearchText.length() > 0) {
				searchImages(strSearchText, 1);
			}
		}
	}// GEN-LAST:event_txtSearchKeyPressed

	/**
	 * searchImages Initiate a search to retrieve a page of matched images.
	 */
	private void searchImages(String strSearchText, int page) {
		if (searcher != null && !searcher.isDone()) {
			// cancel current search to begin a new one
			// only want one image search at a time.
			searcher.cancel(true);
			searcher = null;
		}

		// any parameters on the url GET command needs to URL-encoded
		String strEncodedText = null;
		try {
			strEncodedText = URLEncoder.encode(strSearchText, "UTF-8");
		} catch (UnsupportedEncodingException ex) {
			ex.printStackTrace();
			strEncodedText = strSearchText;
		}

		// remove matched images from any previous search
		listModel = new DefaultListModel();
		listImages.setModel(listModel);
		// clear up any displayed images in preparation to show another
		// selection from the image list
		lblImage.setIcon(null);
		lblImage.setText(null);
		// provide the list model so that the ImageSearcher can publish
		// images to the list immediately as they are available.
		searcher = new ImageSearcher(listModel, API_KEY, strEncodedText, page);
		searcher.addPropertyChangeListener(listenerMatchedImages);

		progressMatchedImages.setIndeterminate(true);

		// start the search!
		searcher.execute();
		// this event thread continues immediately here without blocking
	}

	/**
	 * Create the MainFrame from the Swing event dispatch thread...not this
	 * initial thread
	 * 
	 * @param args
	 *            the command line arguments
	 */
	public static void main(String args[]) {
		API_KEY = getApiKey();
		if (API_KEY == null) {
			System.err.println("You must create a FlickrKey.properties file.");
			System.exit(-1);
		}
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				try {
					UIManager.setLookAndFeel(UIManager
							.getSystemLookAndFeelClassName());
				} catch (Exception ignore) {
				}

				new MainFrame().setVisible(true);
			}
		});
	}

	private static String getApiKey() {
		InputStream is = null;
		Properties props = null;
		String key = null;
		try {
			is = MainFrame.class.getResourceAsStream("FlickrKey.properties");
			props = new Properties();
			props.load(is);
			key = props.getProperty("API_KEY");
		} catch (FileNotFoundException ex) {
			System.err
					.println("The FlickrKey.properties file could not be found.");
		} catch (IOException ioe) {
			System.err
					.println("There was a problem reading the API_KEY property");
		} finally {
			try {
				is.close();
			} catch (Exception e) {
			}
		}
		return key;
	}

	/**
	 * ProgressListener listens to "progress" property changes in the
	 * SwingWorkers that search and load images.
	 */
	class ProgressListener implements PropertyChangeListener {

		ProgressListener(JProgressBar progressBar) {
			this.progressBar = progressBar;
			this.progressBar.setValue(0);
		}

		public void propertyChange(PropertyChangeEvent evt) {
			String strPropertyName = evt.getPropertyName();
			if ("progress".equals(strPropertyName)) {
				progressBar.setIndeterminate(false);
				int progress = (Integer) evt.getNewValue();
				progressBar.setValue(progress);
			}
		}

		private JProgressBar progressBar;
	}

	// Variables declaration - do not modify//GEN-BEGIN:variables
	private javax.swing.JLabel lblImage;
	private javax.swing.JLabel lblImageList;
	private javax.swing.JLabel lblSearch;
	private javax.swing.JLabel lblSelectedImage;
	private javax.swing.JList listImages;
	private javax.swing.JProgressBar progressMatchedImages;
	private javax.swing.JProgressBar progressSelectedImage;
	private javax.swing.JScrollPane scrollImageList;
	private javax.swing.JTextField txtSearch;
	// End of variables declaration//GEN-END:variables
	private DefaultListModel listModel;
	private ImageInfoRenderer renderer;
	private SwingWorker<?, ?> searcher;
	private ProgressListener listenerMatchedImages;
	private ProgressListener listenerSelectedImage;

	private String IMAGE_URL_FORMAT = "http://static.flickr.com/%s/%s_%s.jpg";
	static private String API_KEY;
}
